/*
 * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.huawei.cloudphoneime;

import android.net.LocalSocket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class CaeImeRequest {

    private static final String TAG = "CloudPhoneIme";
    private static final int MAX_MSG_BODY_SIZE = 1024;

    private LocalSocket mLocalSocket;
    private InputStream mInputStream;
    private OutputStream mOutputStream;
    private RequestProcessThread mRequestProcessThread;
    private CaeImeEditChangeListener mTextChangeListener = null;
    private Object mSocketLock = new Object();

    private boolean mIsValid;
    private boolean mIsRun = false;
    private int mHandShakeCode;

    public CaeImeRequest(LocalSocket socket) {
        mLocalSocket = socket;
        mIsValid = false;
        try {
            mInputStream = mLocalSocket.getInputStream();
            mOutputStream = mLocalSocket.getOutputStream();
        } catch (IOException e) {
            CaeImeLog.e(TAG, "Failed to get input or output stream.");
        }
    }

    public int send(byte[] data) {
        if (!mIsValid) {
            return -1;
        }
        int res = data.length;
        synchronized (mSocketLock) {
            try {
                mOutputStream.write(data, 0, data.length);
            } catch (IOException e) {
                CaeImeLog.e(TAG, "Failed to send data.");
                res = -1;
            }
        }
        return res;
    }

    private int sendHandshakePkt() {
        CaeImeProtocol.IMEHandShakePkt handShakePkt = new CaeImeProtocol.IMEHandShakePkt();
        byte[] data = handShakePkt.get();
        int res = data.length;
        synchronized (mSocketLock) {
            try {
                mOutputStream.write(data, 0, data.length);
            } catch (IOException e) {
                CaeImeLog.e(TAG, "Failed to send data.");
                res = -1;
            }
        }
        mHandShakeCode = handShakePkt.getCode();
        return res;
    }

    public int recv(byte[] buf, int len) {
        int nBytes = 0;
        int recvLen = 0;

        while (len > 0 && mIsRun) {
            int canReadBytes = 0;
            try {
                canReadBytes = mInputStream.available();
                if (canReadBytes == 0) {
                    Thread.sleep(100);
                    continue;
                }
            } catch (InterruptedException | IOException e) {
                CaeImeLog.e(TAG, "Wait input stream available failed.");
                break;
            }

            try {
                nBytes = mInputStream.read(buf, recvLen, len);
            } catch (IOException e) {
                CaeImeLog.e(TAG, "Failed to read stream.");
                recvLen = -1;
                break;
            }

            if (nBytes > 0) {
                len -= nBytes;
                recvLen += nBytes;
            }
        }
        return recvLen;
    }

    public int startProcess() {
        mIsRun = true;
        mRequestProcessThread = new RequestProcessThread();
        int ret = 0;
        try {
            mRequestProcessThread.start();
        } catch (Exception e) {
            CaeImeLog.e(TAG, "Start process error.");
            ret = -1;
        }
        return ret;
    }

    public void stopProcess() {
        mIsRun = false;
        try {
            mRequestProcessThread.join();
        } catch (InterruptedException e) {
            CaeImeLog.e(TAG, "Failed to stop process.");
        }
    }

    private void releaseResource() {
        if (mInputStream != null) {
            try {
                mInputStream.close();
            } catch (IOException e) {
                CaeImeLog.e(TAG, "Failed to close input stream.");
            }
            mInputStream = null;
        }
        if (mOutputStream != null) {
            try {
                mOutputStream.close();
            } catch (IOException e) {
                CaeImeLog.e(TAG, "Failed to close output stream.");
            }
            mOutputStream = null;
        }
        if (mLocalSocket != null) {
            try {
                mLocalSocket.close();
            } catch (IOException e) {
                CaeImeLog.e(TAG, "Failed to close local socket.");
            }
            mLocalSocket = null;
        }
    }

    public void setTextChangeListener(CaeImeEditChangeListener listener) {
        mTextChangeListener = listener;
    }


    public int processTextChange(CaeImeProtocol.IMEPktHeader header) {
        int bodyLen = header.getBodyLen();
        if (bodyLen > MAX_MSG_BODY_SIZE) {
            CaeImeLog.e(TAG, "Body size is too large.");
            return -1;
        }
        byte[] buf = new byte[bodyLen];
        int ret = recv(buf, bodyLen);
        if (ret != bodyLen) {
            return -1;
        }
        CaeImeProtocol.IMETextChangePkt textChangePkt = new CaeImeProtocol.IMETextChangePkt();
        ret = textChangePkt.parseBody(buf, 0, bodyLen);
        if (ret < 0) {
            return -1;
        }
        if (mTextChangeListener != null) {
            mTextChangeListener.onRecvText(textChangePkt.getText());
        }
        return 0;
    }

    public int processKeyEvent(CaeImeProtocol.IMEPktHeader header) {
        int bodyLen = header.getBodyLen();
        if (bodyLen > MAX_MSG_BODY_SIZE) {
            CaeImeLog.e(TAG, "Body size is too large.");
            return -1;
        }
        byte[] buf = new byte[bodyLen];
        int ret = recv(buf, bodyLen);
        if (ret != bodyLen) {
            return -1;
        }
        CaeImeProtocol.IMEKeyEventPkt keyEventPkt = new CaeImeProtocol.IMEKeyEventPkt();
        ret = keyEventPkt.parseBody(buf, 0);
        if (ret < 0) {
            return -1;
        }
        if (mTextChangeListener != null) {
            mTextChangeListener.onRecvKeyEvent(keyEventPkt.getKeyCode());
        }
        return 0;
    }

    public int processHandShakePkt(CaeImeProtocol.IMEPktHeader header) {
        int bodyLen = header.getBodyLen();
        if (bodyLen > MAX_MSG_BODY_SIZE) {
            CaeImeLog.e(TAG, "Body size is too large.");
            return -1;
        }
        byte[] buf = new byte[bodyLen];
        int ret = recv(buf, bodyLen);
        if (ret != bodyLen) {
            return -1;
        }
        CaeImeProtocol.IMEHandShakeAckPkt ackPkt = new CaeImeProtocol.IMEHandShakeAckPkt();
        ret = ackPkt.parseBody(buf, 0);
        if (ret < 0) {
            return -1;
        }
        int ackCode = ackPkt.getCode();
        if (ackCode != 2 * mHandShakeCode) {
            CaeImeLog.i(TAG, "Invalid client.");
            return -1;
        }
        return 0;
    }

    public class RequestProcessThread extends Thread {
        @Override
        public void run() {
            CaeImeLog.i(TAG, "Send handshake pkt.");
            int ret = sendHandshakePkt();
            if (ret < 0) {
                CaeImeLog.e(TAG, "Send handshake pkt failed.");
                return;
            }
            mIsValid = false;
            byte[] buf = new byte[CaeImeProtocol.IME_MSG_HEADER_LEN];

            while (mIsRun) {
                CaeImeLog.i(TAG, "Start recv.");
                ret = recv(buf, CaeImeProtocol.IME_MSG_HEADER_LEN);
                CaeImeLog.i(TAG, "Recv size is " + ret);
                if (ret != CaeImeProtocol.IME_MSG_HEADER_LEN) {
                    break;
                }
                CaeImeLog.i(TAG, "Start to process handshake.");
                CaeImeProtocol.IMEPktHeader header = new CaeImeProtocol.IMEPktHeader(buf);
                if (!mIsValid) {
                    CaeImeLog.i(TAG, "Process handshake pkt.");
                    ret = processHandShakePkt(header);
                    if (ret < 0) {
                        break;
                    } else {
                        CaeImeLog.i(TAG, "Handshake success.");
                        mIsValid = true;
                        continue;
                    }
                }
                switch (header.getType()) {
                    case CaeImeProtocol.IME_TEXT_CHANGE_REQ:
                        ret = processTextChange(header);
                        break;
                    case CaeImeProtocol.IME_KEY_EVENT_REQ:
                        ret = processKeyEvent(header);
                        break;
                    default:
                        break;
                }
                if (ret < 0) {
                    break;
                }
            }
            mIsValid = false;
            releaseResource();
        }
    }
}